Skip to content

fix(compression): fit summarizer input to small aux windows instead of lowering the session threshold - #84325

Open
chesterXalan wants to merge 2 commits into
NousResearch:mainfrom
chesterXalan:summary-window-fit-no-threshold-lowering
Open

chesterXalan wants to merge 2 commits into
NousResearch:mainfrom
chesterXalan:summary-window-fit-no-threshold-lowering

Conversation

@chesterXalan

Copy link
Copy Markdown

What does this PR do?

Stops a small-context auxiliary compression model from silently halving the main model's usable context window.

Since #12898, when auxiliary.compression.model resolves to a window smaller than the session's compression threshold, the feasibility check auto-lowers the live session threshold to the aux window. Pairing a 272K main model with a 128K compression model (a natural choice — small models are fast and cheap summarizers) drops the compaction trigger from 231K to 128K: compaction fires at ~47% of the real window, roughly twice as often, each pass discarding conversation detail and invalidating the prompt-cache prefix. Measured on a live gateway, enabling a 128K summary model took a session from ~1 compaction/day to 20+/day with no visible cause beyond a one-line startup log.

The comparison itself is against the wrong quantity. The summarizer request is already bounded — per-message truncation (_CONTENT_MAX 6K chars/message) plus the _SUMMARY_INPUT_MAX_CHARS (160K chars) aggregate cap with an explicit omitted-middle marker — so it never approaches the session threshold it is compared to. What must fit the aux window is that bounded request plus the summary output budget, not the conversation.

The change:

  • check_compression_model_feasibility no longer touches the threshold (or tail_token_budget / threshold_percent). It stashes the resolved aux window on the compressor (summary_model_context_length) and logs one INFO line. The 64K hard floor and the no-provider warning are unchanged.
  • _generate_summary assembles the prompt through a single builder and, when the stashed window is smaller than the assembled request plus the output budget, shrinks the serialized-turns block — head + tail with the existing omitted-middle marker shape — until the whole prompt fits (estimate_tokens_rough over-counts every content class, so a passing fit cannot overflow the real window). A pathological prompt that cannot be shrunk below the floor is sent best-effort; a failure routes through the existing main-model fallback (_fallback_to_main_for_compression), exactly as other aux failures do.
  • _bound_summary_input gains an optional max_chars override used by the fit loop.

Net effect: a small aux model now summarizes a per-call-bounded digest while the main conversation keeps its full window. The threshold — since #80997/#81069 compared against projected real usage — stays a property of the main model only.

Related: #12898 (introduced the auto-lower alongside the 64K floor — the floor is kept), #67422 (the threshold-suggestion math in the removed warning; the suggestion is no longer needed because nothing needs correcting), #8499 (auxiliary.compression.context_length config override — still honored, it feeds the stashed window), #52392 (fallback-chain context screening — untouched).

Related Issue

No open issue describes this defect; the origin PR and adjacent reports are linked above.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • agent/conversation_compression.py — feasibility check stashes the aux window instead of lowering the session threshold; removes the auto-lower bookkeeping (tail_token_budget lockstep, threshold_percent rewrite) and its multi-line user warning, which no longer has anything to warn about.
  • agent/context_compressor.py — new summary_model_context_length field; _bound_summary_input(max_chars=...) override; _generate_summary prompt assembly extracted into one builder + per-call fit loop with a floor guard and best-effort logging.
  • tests/agent/test_summary_window_fit.py — new: feasibility stash semantics (threshold untouched, no warning), per-call trim fits the window on CJK-dense content, fit inactive without a stashed window or when the main model summarizes, max_chars override.
  • tests/run_agent/test_compression_feasibility.py — three tests updated from auto-lower semantics to stash-and-fit semantics; the gateway-replay test now exercises the surviving no-provider warning; hard-floor rejection and config-override tests unchanged.

How to Test

  1. pytest tests/agent/test_summary_window_fit.py tests/run_agent/test_compression_feasibility.py -q — 16 passed.
  2. pytest tests/agent tests/run_agent -q -k "compress or compaction or summary or feasib" — 711 passed on this branch.
  3. Live repro: set auxiliary.compression.{provider,model} to any 128K model under a 200K+ main model. Before: startup logs auto-lowered session threshold to 128000 and long sessions compact at ~half the real window, several times per hour. After: the threshold log line is gone, Preflight compression still fires at the configured threshold, and when compaction runs the log shows Summarizer input trimmed to fit <model>'s 128000-token window while the summary call succeeds within it.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run the compression/summary/feasibility suites listed above (711 passed); the full tests/ run on my machine has environment-dependent failures (i18n catalogs, models.dev fetch, etc.) that are identical on clean main
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 15 (arm64), Python 3.11
  • Verified live on a production gateway (Codex Responses transport, 272K main model): deploy, graceful restart, healthy

Documentation & Housekeeping

  • I've updated relevant documentation (docstrings) — behavior documented on _bound_summary_input, the fit loop, and the feasibility stash
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A (no config changes; existing keys keep their meaning)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) — N/A (pure Python)
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

…f lowering the session threshold

A summary model whose context window is smaller than the session
threshold used to trigger an auto-lower of the live threshold to the
aux window — halving a 272K main model's usable context when paired
with a 128K compression model: compaction fires at ~47% of the real
window, twice as often, discarding conversation detail early and
breaking the prompt-cache prefix each time.

The comparison itself was against the wrong quantity: the summariser
request is already bounded (per-message truncation plus the
_SUMMARY_INPUT_MAX_CHARS aggregate cap), so it never approaches the
session threshold. What actually needs to fit the aux window is that
bounded request — not the conversation.

check_compression_model_feasibility now stashes the resolved aux
window on the compressor and leaves the threshold alone; the 64K hard
floor is unchanged. _generate_summary assembles the prompt through a
single builder and, when the stashed window is smaller than the
assembled request plus the summary output budget, shrinks the
serialized-turns block (head+tail, explicit omitted-middle marker —
the same shape as the existing aggregate cap) until it fits.
estimate_tokens_rough over-counts, so a passing fit cannot overflow
the real window; a still-oversized pathological prompt is sent
best-effort and a failure routes through the existing main-model
fallback.

_bound_summary_input gains an optional max_chars override used by the
fit loop.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/compression Context compression and continuation sessions sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 12, 2026
@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference, author can ignore or act on any point.

PR: fix(compression): fit summarizer input to small aux windows instead of lowering the session threshold

  1. Overlaps feat(compression): fold oversized windows through sequential chunk summaries #84336 (agent/context_compressor.py, agent/conversation_compression.py): feat(compression): fold oversized windows through sequential chunk summaries #84336 contains the identical feasibility rewrite and per-call fit plus a sequential-fold mechanism on top. If both are open they conflict on the same hunks — worth confirming the intended stacking (e.g. this lands first, feat(compression): fold oversized windows through sequential chunk summaries #84336 rebases) so the duplicate isn't reviewed twice.

  2. User-facing warning removed (conversation_compression.py): previously a small aux model produced a visible "auto-lowered this session's threshold" message with config.yaml guidance; now it is a silent logger.info. The fix is arguably better (the threshold no longer needs lowering), but users with a genuinely too-small aux model now get silently trimmed summaries and no pointer to auxiliary.compression.model. A cooldown-bounded one-time status emission when the per-call fit actually engages would preserve the diagnostic without the noise.

  3. Fit loop can exit non-converged without logging (context_compressor.py, _generate_summary): the warning fires only when content hits the 8000-char floor; if the 4-iteration loop exhausts while content is still above the floor and the prompt is still over _fit_budget, it breaks silently and sends a possibly-oversized prompt. Consider logging when the loop exits without converging (iteration cap), not just on the floor case.

  4. Stashed window can go stale mid-session (minor): summary_model_context_length is set once at session start by check_compression_model_feasibility; if the aux model/window changes later (config reload, update_model), the fit targets the old window — a shrunken window could overflow, a grown one just over-trims. Worth refreshing the stash wherever update_model updates the other derived budgets.

Addresses automated-review findings on the window-fit change:

* Log when the trim loop exits via its iteration cap while the prompt is
  still over the fit budget — previously only the content-floor exit
  warned, and a non-converged prompt was sent silently. (Reaching the
  cap without converging requires a scaffold-dominated prompt, so this
  is defensive, but a silently oversized send deserves a trace.)
* Restore a user-facing diagnostic for small aux windows, without the
  old auto-lower warning's noise: when the per-call fit actually trims
  the summarizer input, compress_context emits a one-time-per-session
  status pointing at auxiliary.compression.model — mirroring the
  existing _last_summary_fallback_used surfacing pattern. Sessions
  whose windows never need trimming see nothing.
@chesterXalan

Copy link
Copy Markdown
Author

Thanks for the review — all four points addressed or answered:

  1. Stacking: confirmed — this PR lands first; feat(compression): fold oversized windows through sequential chunk summaries #84336 is stacked on it (stated in its description's first line) and has just been rebased on this branch's updated head, so the overlap is the stack, not a duplicate.
  2. User-facing warning: restored in 6ce35a12e, without the old warning's noise — when the per-call fit actually trims, compress_context now emits a one-time-per-session status pointing at auxiliary.compression.model (mirrors the existing _last_summary_fallback_used surfacing). Sessions whose windows never need trimming see nothing, which the session-start auto-lower warning couldn't offer.
  3. Non-converged exit: also in 6ce35a12e — the iteration-cap exit now logs a warning distinct from the content-floor exit. (Reaching it requires a scaffold-dominated prompt, so it's defensive, but agreed a silently oversized send deserves a trace.)
  4. Stale stash: the stash tracks the auxiliary model, which update_model (main-model switches) doesn't change — aux config is resolved per session like every other auxiliary.* setting. The one mid-session aux transition that does exist (_fallback_to_main_for_compression) clears summary_model, which gates the fit, so a stale window is inert there. Happy to wire a refresh if there's a live mid-session aux-switch path I've missed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/compression Context compression and continuation sessions comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants